﻿#############################
# File Auto zip Core- By Yingestech
#
# Can use demo main as a rederence
#
#############################

#! python3.11
# importing required modules
import os, time, shutil, subprocess, re
from tkinter import messagebox as mb


class log_folder_dirs:

    class log_folder_dir:
        '类:生产log每日文件夹'

        path_7za = r'.\7z2201-extra\7za.exe'

        def __init__(self, root: str, relative_path: list[str],
                     save_level: int):
            # C:\AA1 Result=root / BYD X1F=level 1/ 2023=level 2。。。
            # C:\AA1 Result\BYD X1F\2023\08\11
            dash = '_'
            slash = '\\'
            self.new_name = dash.join(relative_path)
            self.path = root + slash + slash.join(relative_path)
            self.target_path = root + slash + slash.join(
                relative_path[:save_level])
            self.m_time = os.stat(self.path).st_mtime

        def clean_folder(
            self,
            time_limit: int,
            match_str: list[str],
            mark_level: int,
            re_ph: list[str] = [],
        ) -> (int, int, bool):
            """Clean the folder with specify condition

            Args:
                time_limit (int): Time limit Second.
                match_str (list[str]): Match string
                mark_level (int): the mark in which level
                re_ph (list[str], optional): leave it empty.

            Returns:
                int: File QTY cleaned
                int: File QTY skipped clean
                bool: all cleaned?
            """
            mark = '-cld'
            slash = '\\'
            cleaned = 0
            skipped = 0
            all_cleaned = True
            current_time = int(time.time())
            match_level = len(match_str)
            if match_level <= 0 or mark_level > match_level:
                return (-1, 0, False)  # Input Error
            ab_ph = slash.join([self.path, *re_ph])
            if mark_level == 0:
                if ab_ph[-len(mark):] == mark:
                    return (0, 0, True)  # Skipped Archive cause with the name
            for file_name in os.listdir(ab_ph):
                file_abph = os.path.join(ab_ph, file_name)
                matYN = re.search(match_str[0], file_name)
                if matYN != None:
                    if match_level == 1:
                        file_time = int(os.stat(file_abph).st_mtime)
                        if current_time - file_time < time_limit:
                            all_cleaned = False
                            continue
                        try:
                            if os.path.isfile(file_abph):
                                os.remove(file_abph)
                            else:
                                shutil.rmtree(file_abph)
                        except:
                            all_cleaned = False
                            skipped += 1
                            continue
                        cleaned += 1
                    else:
                        if os.path.isdir(file_abph):
                            ac = True
                            (cld, skpd, ac) = self.clean_folder(
                                time_limit,
                                match_str[1:],
                                mark_level - 1,
                                [*re_ph, file_name],
                            )
                            if cld >= 0:
                                cleaned += cld
                                skipped += skpd
                                all_cleaned = all_cleaned and ac
            if mark_level == 0 and all_cleaned:
                level = len(re_ph)
                try:
                    os.rename(ab_ph, ab_ph + mark)
                except:
                    all_cleaned = False
                if level == 0:
                    self.path = ab_ph + mark
            return (cleaned, skipped, all_cleaned)

        def sevenzip(self, remian_time: int) -> int:
            """Archive file with 7za.exe

            Args:
                remian_time (int): Time to Archive

            Returns:
                int: 0 -- File Archived Skipped \n
                int: 1 -- Archived successfuly \n
                int:-1 -- Archived with some error \n
                int:-2 -- Another archiving process is working!
            """
            now = time.time()
            if (now - self.m_time) > remian_time:
                timemark = "-" + time.strftime("%y%m%d%H%M%S",
                                               time.localtime(now))
                archive_list_txt = self.path + "\\Archive Lists_By_Yingestech.txt"
                if os.path.exists(archive_list_txt):
                    try:
                        os.remove(archive_list_txt)
                    except:
                        return -2
                content = (self.path + ' ==> ' + self.new_name + timemark +
                           '.7z - By_Yingestech\r\nList:\n' +
                           '\n'.join(os.listdir(self.path)))
                with open(archive_list_txt, 'a+', encoding='utf-8') as fs:
                    fs.write(content)
                alt = open(archive_list_txt)
                newpath_newname = (self.target_path + "\\" + self.new_name +
                                   timemark + ".7z")
                new_old_Path_Name = "\"" + newpath_newname + "\" \"" + self.path + "\""
                path_7za = "\"" + self.path_7za + "\""
                cmd = path_7za + " a -bso2 -bse2 -bsp2 -mmt2 " + new_old_Path_Name
                ret = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
                for line in iter(ret.stdout.readline, 'b'):
                    line = line.rstrip().decode('utf8')
                    if (subprocess.Popen.poll(ret) is not None) and line == "":
                        break
                ret.stdout.close()
                alt.close()
                if ret.returncode == 0:
                    print(newpath_newname + " archived!\n")
                    # delete zipped file
                    while True:
                        try:
                            if os.path.isfile(self.path):
                                os.remove(self.path)
                            else:
                                shutil.rmtree(self.path)
                        except:
                            mb.showinfo(
                                "错误！！！",
                                "压缩完成，删除" + self.path + "出现错误，请手动清理相关进程后点击确认。")
                            continue
                        break
                    return 1
                else:
                    return -1
            else:
                return 0

    def __init__(self, root_dir: str, list_level=4, save_level=2):
        self.list_level = list_level
        self.save_level = save_level
        if self.list_level < self.save_level:
            print(
                "Error:Parameter intut with issue, required:list_level>=save_level"
            )
            return
        self.root_dir = root_dir
        self.part_folder_list = self.__get_all_folder()

    def __get_all_folder(self,
                         working_dir="",
                         rel_path: list[str] = [],
                         i=1) -> list[log_folder_dir]:
        """Traverse all folder in the path, --Private methord--

        Args:
            working_dir (str, optional): root folder input. Defaults to "".
            rel_path (list[str], optional): Should be [].
            i (int, optional): Should be 1.

        Returns:
            list[log_folder_dir]: _description_
        """
        empty_lis: list[str] = []
        if i > self.list_level:
            return empty_lis
        folder_lis: list[self.log_folder_dir] = []
        if i == 1:
            folders = os.listdir(self.root_dir)
            working_dir = self.root_dir
        else:
            folders = os.listdir(working_dir)
        for folder in folders:
            re_path_add = rel_path + [folder]
            root_add_folder = os.path.join(working_dir, folder)
            if os.path.isfile(root_add_folder):
                continue
            if i < self.list_level:
                folder_lis = folder_lis + self.__get_all_folder(
                    root_add_folder, re_path_add, i + 1)
            else:
                folder_lis.append(
                    self.log_folder_dir(self.root_dir, re_path_add,
                                        self.save_level))
        return folder_lis

    def clean_folders(self, remain_time: int, match: list[str],
                      mark_level: int):
        """Clean folder with specify condition

        Args:
            remain_time (int): Clean the file last for how long time
            match (list[str]): Match string
            mark_level (int): Mark '-cleaned'in which folder

        Returns:
            _type_: how many folder or file that cleaned
        """
        cleaned = 0
        skipped = 0
        ac = True
        for part_folder in self.part_folder_list:
            (cld, sk, ac) = part_folder.clean_folder(remain_time, match,
                                                     mark_level)
            if cld > 0:
                cleaned += cld
                skipped += sk
        return (cleaned, skipped, ac)

    def sevenzips(self, remain_time, zips_in_one_time=5) -> int:
        """Archive Logs with 7za.exe

        Args:
            remain_time (_type_): _description_
            zips_in_one_time (int, optional): how many logs should be archived in this time. Defaults to 5.

        Returns:
            int: Archived QTY Out\n
            if count = -1 Zipped with Error\n
            if Count = -2 Zipped Terminated Because Other process is Running
        """
        count = 0
        for folder in self.part_folder_list:
            if count > zips_in_one_time - 1:
                break
            ar_result = folder.sevenzip(remain_time)
            if ar_result == 1:
                count += 1
            elif ar_result == 0:
                pass
            elif ar_result == -1:
                count = -1  # error 1
                break
            elif ar_result == -2:
                count = -2  # error 2
                break
        return count
